// ----------------------------
// projects/c++/date/TestDate.h
// Copyright (C) 2009
// Glenn P. Downing
// ----------------------------

#ifndef TestDate_h
#define TestDate_h

// --------
// includes
// --------

#include <cstring>   // strcmp
#include <sstream>   // ostringstream
#include <stdexcept> // invalid_argument
#include <string>    // string, ==
#include <utility>   // !=, <=, >, >=

#include "cppunit/TestFixture.h"             // TestFixture
#include "cppunit/extensions/HelperMacros.h" // CPPUNIT_TEST, CPPUNIT_TEST_SUITE, CPPUNIT_TEST_SUITE

#include "Date.h"

using namespace std::rel_ops;

// --------
// TestDate
// --------

struct TestDate : CppUnit::TestFixture {
    // ----------------
    // test_constructor
    // ----------------

	void debug() {
		try {
			const Date::Date<int> x(31,12,1999);
			const Date::Date<int> y(31,12,2099);

			std::cout << std::endl << x << std::endl;
			std::cout << x.to_days() << std::endl;
			std::cout << (x+0) << std::endl;
			std::cout << (x+0).to_days() << std::endl;

			std::cout << std::endl << (y-x) << std::endl;

			std::cout << (x+(y-x)) << std::endl;
			std::cout << (x+(y-x)).to_days() << std::endl;
			std::cout << y << std::endl;
			std::cout << y.to_days() << std::endl;
		}
		catch (...){ std::cout << "UHHH OHHHH" << std::endl; }
	}

    void test_constructor () {
        try {
            const Date::Date<int> x(0, 1, 1600);
            CPPUNIT_ASSERT(false);}
        catch (std::invalid_argument& e) {
            CPPUNIT_ASSERT(std::strcmp(e.what(), "Date::Date()") == 0);}
        try {
            const Date::Date<int> x(1, 0, 1600);
            CPPUNIT_ASSERT(false);}
        catch (std::invalid_argument& e) {
            CPPUNIT_ASSERT(std::strcmp(e.what(), "Date::Date()") == 0);}
        try {
            const Date::Date<int> x(1, 1, 1599);
            CPPUNIT_ASSERT(false);}
            catch (std::invalid_argument& e) {
            CPPUNIT_ASSERT(std::strcmp(e.what(), "Date::Date()") == 0);}
        try {
	        const Date::Date<int> x(0, 0, 1599);
	        CPPUNIT_ASSERT(false);}
	        catch (std::invalid_argument& e) {
	        CPPUNIT_ASSERT(std::strcmp(e.what(), "Date::Date()") == 0);}
    }

    // -------------
    // test_equal_to
    // -------------

    void test_equal_to () {
        try {
            const Date::Date<int> x(28, 2, 2000);
            const Date::Date<int> y(28, 2, 2000);
            CPPUNIT_ASSERT(x == y);
            CPPUNIT_ASSERT(!(x != y));}
        catch (std::invalid_argument& e) {
            CPPUNIT_ASSERT(false);}}

    void test_less () {
        try {
            const Date::Date<int> x(27, 2, 2000);
            const Date::Date<int> y(28, 2, 2000);
            CPPUNIT_ASSERT(x <  y);
            CPPUNIT_ASSERT(x <= y);
            CPPUNIT_ASSERT(!(x >  y));
            CPPUNIT_ASSERT(!(x >= y));}
        catch (std::invalid_argument& e) {
            CPPUNIT_ASSERT(false);}}

    /**
     * testing plus equal
     */
    template <typename T>
    void test_plus_equal () {
        try {
            Date::Date<T> x(1, 1, 2000);
            const Date::Date<T> y(1, 1, 2001);
            CPPUNIT_ASSERT((x += 366) == y);}
        catch (std::invalid_argument& e) {
            CPPUNIT_ASSERT(false);}}


    template <typename T>
    void test_plusExtensive2000 () {
    try {
    	{
	        const Date::Date<T> x(31, 12, 1999);
	        const Date::Date<T> y(31, 1, 2000);
	        const Date::Date<T> z(1,1,2000);
/*
	        std::cout << z << std::endl << z.to_days() << std::endl;
	        std::cout << (z+(T)366) << std::endl << (z+(T)366).to_days() << std::endl;
			std::cout << std::endl;

			std::cout << x << std::endl;
			std::cout << x.to_days() << std::endl;
			std::cout << (x+(T)31) << std::endl;
			std::cout << (x+(T)31).to_days() << std::endl;
			std::cout << y << std::endl;
			std::cout << y.to_days() << std::endl;

			std::cout << ((x + (T)31) == y)  << std::endl;
*/

	        CPPUNIT_ASSERT((x + (T)31) == y);
    	}
    	{
    		const Date::Date<T> x(31, 12, 1999);
	        const Date::Date<T> y(1, 2, 2000);
	        CPPUNIT_ASSERT((x + (T)32) == y);
    	}
    	{
			const Date::Date<T> x(31, 12, 1999);
	        const Date::Date<T> y(29, 2, 2000);
	        CPPUNIT_ASSERT((x + (T)60) == y);
    	}
    	{
    		//testing 1st century
			const Date::Date<T> x(31, 12, 1999);
			const Date::Date<T> y(31, 12, 2099);

			CPPUNIT_ASSERT((x + (T)36525) == y);

    	}
    	{
    		//testing 2nd century
    		const Date::Date<T> x(31, 12, 2099);
			const Date::Date<T> y(31, 12, 2199);
			CPPUNIT_ASSERT((x + (T)36524) == y);

    	}
    	{
			//testing 3rd century
			const Date::Date<T> x(31, 12, 2199);
			const Date::Date<T> y(31, 12, 2299);
			CPPUNIT_ASSERT((x + (T)36524) == y);

		}
    	{
			//testing 4rd century
			const Date::Date<T> x(31, 12, 2299);
			const Date::Date<T> y(31, 12, 2399);
			CPPUNIT_ASSERT((x + (T)36524) == y);
		}
    	{

    		const Date::Date<T> x(31, 12, 1999);
			const Date::Date<T> y(31, 12, 2399);
			CPPUNIT_ASSERT((x + (T)146097) == y);
    	}
    }
    catch (std::invalid_argument& e) {
        CPPUNIT_ASSERT(false);}}

    template <typename T>
    void test_minusExtensive2000 () {
        try {
        	{
    	        const Date::Date<T> x(31, 12, 1999);
    	        const Date::Date<T> y(31, 1, 2000);
    	        CPPUNIT_ASSERT((x - (T)(-31)) == y);
        	}
        	{
        		const Date::Date<T> x(31, 12, 1999);
    	        const Date::Date<T> y(1, 2, 2000);
    	        CPPUNIT_ASSERT((x - (T)-32) == y);
        	}
        	{
    			const Date::Date<T> x(31, 12, 1999);
    	        const Date::Date<T> y(29, 2, 2000);
    	        CPPUNIT_ASSERT((x - (T)-60) == y);
        	}
        	{
        		//testing 1st century
    			const Date::Date<T> x(31, 12, 1999);
    			const Date::Date<T> y(31, 12, 2099);
    			CPPUNIT_ASSERT((x - (T)-36525) == y);

        	}
        	{
        		//testing 2nd century
        		const Date::Date<T> x(31, 12, 2099);
    			const Date::Date<T> y(31, 12, 2199);
    			CPPUNIT_ASSERT((x - (T)-36524) == y);

        	}
        	{
    			//testing 3rd century
    			const Date::Date<T> x(31, 12, 2199);
    			const Date::Date<T> y(31, 12, 2299);
    			CPPUNIT_ASSERT((x - (T)-36524) == y);

    		}
        	{
    			//testing 4rd century
    			const Date::Date<T> x(31, 12, 2299);
    			const Date::Date<T> y(31, 12, 2399);
    			CPPUNIT_ASSERT((x - (T)-36524) == y);
    		}
        	{

        		const Date::Date<T> x(31, 12, 1999);
    			const Date::Date<T> y(31, 12, 2399);
    			CPPUNIT_ASSERT((x - (T)-146097) == y);
        	}
        }
        catch (std::invalid_argument& e) {
            CPPUNIT_ASSERT(false);}}

    template <typename T>
    void test_plus_extensive(){
    	try{
    		{   //with a century leap year in question
        		const Date::Date<T> x(28, 02, 2000);
    			const Date::Date<T> y(31, 12, 2000);
/*    			std::cout << x << std::endl;
    			std::cout << x.to_days() << std::endl;
    			std::cout << (x+307) << std::endl;
    			std::cout << (x+307).to_days() << std::endl;
    			std::cout << y << std::endl;
    			std::cout << y.to_days() << std::endl;
*/    			CPPUNIT_ASSERT((x + 307) == y);
        	}
    		{   //without a leap year in question
        		const Date::Date<T> x(28, 2, 1900);
    			const Date::Date<T> y(31, 12, 1900);
    			CPPUNIT_ASSERT((x + 306) == y);
        	}
    		{   //with a regular leap year in question
        		Date::Date<T> x(28, 02, 1904);
    			const Date::Date<T> y(31, 12, 1904);
    			CPPUNIT_ASSERT((x + 307) == y);
        	}
    		{   //without a regular leap year in question
        		Date::Date<T> x(28, 02, 1903);
    			const Date::Date<T> y(31, 12, 1903);
    			CPPUNIT_ASSERT((x + 306) == y);
        	}
    		{
    			const Date::Date<T> x(1,1,1600);
    			CPPUNIT_ASSERT(x == (x + (T)0));
    		}

        }
        catch (std::invalid_argument& e) {
            CPPUNIT_ASSERT(false);}}


    /**
     * Tests all the functions that throw invalid argument exceptions except the constructor
     */
    void test_invalid_arguments(){
    	try
    	{
    		const Date::Date<int> x(1, 1, 1600);
    		Date::Date<int> y = x + (-1);
    		CPPUNIT_ASSERT(false);
    	}catch(std::invalid_argument &e){
    		CPPUNIT_ASSERT(!std::strcmp(e.what(),"Date::operator+(Date<T>, T)"));
    	}
    	try
    	{
    		const Date::Date<int> x(1, 1, 2000);
    		Date::Date<int> y = x - (146098);
    		CPPUNIT_ASSERT(false);
    	}catch(std::invalid_argument &e){
    		CPPUNIT_ASSERT(true);
    		CPPUNIT_ASSERT(!std::strcmp(e.what(),"Date::operator-(Date<T>, T)"));
    	}

    	try
    	{
    		const Date::Date<int> x(31, 12, 1999);
    		Date::Date<int> y = x + (-146097);
    		CPPUNIT_ASSERT(false);
    	}catch(std::invalid_argument &e){
    		CPPUNIT_ASSERT(true);
    		CPPUNIT_ASSERT(!std::strcmp(e.what(),"Date::operator+(Date<T>, T)"));

    	}

    	try
    	{
    		Date::Date<int> x(31, 12, 1999);
    		x += -146097;
    		CPPUNIT_ASSERT(false);
    	}catch(std::invalid_argument &e){
    		CPPUNIT_ASSERT(true);
    		CPPUNIT_ASSERT(!std::strcmp(e.what(),"Date::operator+=(const T&)"));
    	}

    	try
    	{
    		Date::Date<int> x(31, 12, 1999);
    		x -= 146097;
    		CPPUNIT_ASSERT(false);
    	}catch(std::invalid_argument &e){
    		CPPUNIT_ASSERT(true);
    		CPPUNIT_ASSERT(!std::strcmp(e.what(),"Date::operator-=(const T&)"));
    	}
    }

    template < typename T>
    void test_minus_equal () {
        try {
            const Date::Date<T> x(1, 1, 2000);
            Date::Date<T> y(1, 1, 2001);
            CPPUNIT_ASSERT((y -= 366) == x);}
        catch (std::invalid_argument& e) {
            CPPUNIT_ASSERT(false);}}

    void test_output () {
        try {
            const Date::Date<int> x(16, 6, 2008);
            std::ostringstream out;
            out << x;
            CPPUNIT_ASSERT(out.str() == "16 Jun 2008");}
        catch (std::invalid_argument& e) {
            CPPUNIT_ASSERT(false);}}

    /**
     * tests the variant of - operator: Date<T> - Date<T>
     */
    template <typename T>
    void test_minus_dates(){
    	try{
    		{
    			T result = 146097;
    			Date::Date<T> x(1,1,1600);
    			Date::Date<T> y(1,1,2000);
    			CPPUNIT_ASSERT((y - x) == result);
    			CPPUNIT_ASSERT((x - y) == -result);
    		}

    		{
    			T result = 60;
    			Date::Date<T> x(31,12,1999);
    			Date::Date<T> y(29,2,2000);
    			CPPUNIT_ASSERT((y - x) == result);
    			CPPUNIT_ASSERT((x - y) == -result);
    		}
    		{
    			T result = 365;
    			Date::Date<T> x(1,12,1600);
    			Date::Date<T> y(1,12,1601);
    			CPPUNIT_ASSERT((y - x) == result);
    			CPPUNIT_ASSERT((x - y) == -result);
    		}
    		{
    			T result = 366;
    			Date::Date<T> x(1,12,1603);
    			Date::Date<T> y(1,12,1604);
    			//cout<<(y-x);
    			CPPUNIT_ASSERT((y - x) == result);
    			CPPUNIT_ASSERT((x - y) == -result);
    		}
    		{
    			T result = 365;
    			Date::Date<T> x(28,2,1603);
    			Date::Date<T> y(28,2,1604);
    			CPPUNIT_ASSERT((y - x) == result);
    			CPPUNIT_ASSERT((x - y) == -result);
    		}

    	}catch(std::invalid_argument& e){
    		CPPUNIT_ASSERT(false);
    	}
    }

   /**
    * Tests the various cases for the leap year
    */
    template <typename T>
    void test_leap_year () {
        try { //%400 == 0
            CPPUNIT_ASSERT( Date::Date<T>(1, 1, 2000).leap_year());}
        catch (std::invalid_argument& e) {
            CPPUNIT_ASSERT(false);}

        try { //%400 != 0
           CPPUNIT_ASSERT( !Date::Date<T>(1, 1, 1900).leap_year());}
       catch (std::invalid_argument& e) {
           CPPUNIT_ASSERT(false);}

       try { //%4==0 && %100 != 0
         CPPUNIT_ASSERT( Date::Date<T>(1, 1, 1904).leap_year());}
     catch (std::invalid_argument& e) {
         CPPUNIT_ASSERT(false);}

    }

    void test_my_assumptions() {

    	const Date::Date<int> a(1,1,1600);
    	const Date::Date<int> b(1,1,1601);
    	const Date::Date<int> c(1,1,1602);
    	const Date::Date<int> d(1,1,1604);
    	const Date::Date<int> e(1,1,1700);
    	const Date::Date<int> f(1,1,1800);
    	const Date::Date<int> g(1,1,2000);

    	CPPUNIT_ASSERT(a.to_days() == 0);
    	CPPUNIT_ASSERT(b.to_days() == 366);
    	CPPUNIT_ASSERT(c.to_days() == 731);	// (366 + 365)

    	CPPUNIT_ASSERT(d.to_days() == 1461);

    	CPPUNIT_ASSERT(e.to_days() == 36525);
    	CPPUNIT_ASSERT(f.to_days() == 73049);	// (36525 + 36524)

    	CPPUNIT_ASSERT(g.to_days() == 146097);
    }

    // -----
    // suite
    // -----

    CPPUNIT_TEST_SUITE(TestDate);
//    CPPUNIT_TEST(debug);
    CPPUNIT_TEST(test_constructor);
    CPPUNIT_TEST(test_equal_to);
    CPPUNIT_TEST(test_less);
    CPPUNIT_TEST(test_plus_equal<int>);
    CPPUNIT_TEST(test_plus_equal<long>);
    CPPUNIT_TEST(test_plusExtensive2000<int>);
    CPPUNIT_TEST(test_plusExtensive2000<long>);
    CPPUNIT_TEST(test_invalid_arguments);
    CPPUNIT_TEST(test_minus_equal<int>);
    CPPUNIT_TEST(test_minus_equal<long>);
    CPPUNIT_TEST(test_minus_dates<int>);
    CPPUNIT_TEST(test_minusExtensive2000<int>);
    CPPUNIT_TEST(test_minusExtensive2000<long>);
    CPPUNIT_TEST(test_output);
    CPPUNIT_TEST(test_plus_extensive<int>);
    CPPUNIT_TEST(test_leap_year<int>);
    CPPUNIT_TEST(test_leap_year<long>);
    CPPUNIT_TEST(test_my_assumptions);
    CPPUNIT_TEST_SUITE_END();};

#endif // TestDate::Date<int>_h

